﻿using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace BreukTrainer.BreukModel
{
    /// <summary>
    /// Een klasse die een breuk voorstelt
    /// </summary>
    public struct Breuk : IComparable<Breuk>
    {
        /// <summary>
        /// Bovenste deel van een breuk.
        /// </summary>
        private int teller;

        /// <summary>
        /// Properties voor <see cref="teller"/>.
        /// </summary>
        /// <returns>Een <see langword="integer"/>.</returns>
        public int Teller
        {
            get
            {
                return teller;
            }
            set
            {
                teller = value;
            }
        }
        
        /// <summary>
        /// Onderste deel van een breuk.
        /// </summary>
        private int noemer;

        /// <summary>
        /// Properties voor <see cref="noemer"/>.
        /// </summary>
        /// <exception cref="System.ArithmeticException">Deze exception zal gegooid worden wanneer je <paramref name="noemer"/> op 0 probeert te setten.</exception>
        /// <remarks>Set de <see cref="noemer"/> behalve als deze 0 is.</remarks>
        /// <returns>Een <see langword="integer"/>.</returns>
        public int Noemer
        {
            get
            {
                return noemer;
            }
            set
            {
                if (value == 0)
                {
                    throw new ArithmeticException("Ben je gek? je bent aan het delen door nul! Dat kan toch niet man!");
                }
                noemer = value;
            }
        }

        /// <summary>
        /// <see langword="Static"/> <see langword="Random"/> met als seed de tijd.
        /// </summary>
        /// <remarks>De seed is het aantal ticks welke de tijd van deze Instance voorstellen.</remarks>
        private static Random random = new Random((int)DateTime.Now.Ticks);

        /// <summary>
        /// Geeft een Breuk terug waarbij de <see cref="teller"/> en <see cref="noemer"/> random bepaald worden en liggen tussen een onder- en bovengrens.
        /// </summary>
        /// <returns>Een Breuk.</returns>
        /// <param name="ondergrens">Een <see langword="integer"/> welke aangeeft wat het kleinste getal van <see cref="teller"/> en <see cref="noemer"/> mag zijn.</param>
        /// <param name="bovengrens">Een <see langword="integer"/> welke aangeeft wat het grootste getal van <see cref="teller"/> en <see cref="noemer"/> mag zijn.</param>
        public static Breuk RandomBreuk(int ondergrens, int bovengrens)
        {
            int teller = random.Next(ondergrens, bovengrens);
            int noemer = random.Next(ondergrens, bovengrens);
            noemer += noemer == 0 ? 1 : 0;
            return new Breuk(teller, noemer);
        }

        /// <summary>
        /// Een static functie die een string omzet naar een breuk
        /// </summary>
        /// <param name="breuk">Een string die eruit ziet als een breuk gescheiden door een slash</param>
        /// <remarks>Als de breuk door een ander teken dan een slash gescheiden wordt, geeft dit een error</remarks>
        /// <returns>De breuk nu van de klasse <see cref="Breuk"/>.</returns>

        public static Breuk Parse(string breuk)
        {
            Regex reg = new Regex("^-?\\d+(/([1-9]|\\d{2,}))?$");
            if (!reg.IsMatch(breuk))
            {
                throw new ArgumentException("De megegeven string (" + breuk + ") kan niet worden omgezet in een breuk");
            }
            if (breuk.Contains('/'))
            {
                string[] breukarr = breuk.Split('/');
                int teller = int.Parse(breukarr[0]);
                int noemer = int.Parse(breukarr[1]);
                return new Breuk(teller, noemer);
            }
            return new Breuk(int.Parse(breuk), 1);
        }

        /// <summary>
        /// Maakt een nieuwe Breuk aan.
        /// </summary>
        /// <remarks>Schrijft naar Debug.</remarks>
        /// <returns>void</returns>
        /// <param name="teller">Een <see langword="integer"/> voor de <see cref="teller"/>.</param>
        /// <param name="noemer">Een <see langword="integer"/> voor de <see cref="noemer"/>.</param>
        public Breuk(int teller, int noemer)
        {
            Debug.WriteLine("Een niewe Breuk word aangemaakt met als teller " + teller + " en als noemer " + noemer + " !");  
            this.teller = teller;
            this.noemer = noemer;
        }

        /// <summary>
        /// Berekent de Grootste gemene deler van de Breuk.
        /// </summary>
        /// <returns>Een Integer.</returns>
        public int GrootsteGemeneDeler()
        {
            return GGD(this.teller, this.noemer);
        }

        private int GGD(int x, int y)
        {
            if (y == 0)
            {
                return x;
            }
            else
            {
                return GGD(y, x % y);
            }
        }

        /// <summary>
        /// Berekent het Kleinst gemeen veelvoud van de Breuk.
        /// </summary>
        /// <remarks>Gebruikt GrootsteGemeneDeler.</remarks>
        /// <returns>Een Integer.</returns>
        public int KleinsteGemeeneVeelvoud()
        {
            return Math.Abs(teller * noemer) / GrootsteGemeneDeler();        
        }

        /// <summary>
        /// Geeft een Breuk terug welke een vereenvoudiging is van deze Breuk.
        /// </summary>
        /// <remarks>Gebruikt GrootsteGemeneDeler.</remarks>
        /// <returns>De vereenvoudigde Breuk van deze Breuk.</returns>
        public Breuk Vereenvoudig()
        {
            int deler = GrootsteGemeneDeler();
            int nteller = this.teller, nnoemer = this.noemer;
            if (deler > 1)
            {
                nteller = this.teller / deler;
                nnoemer = this.noemer / deler;
            }
            return new Breuk(nteller, nnoemer);
        }

        /// <summary>
        /// Telt de gegeven Breuk op bij de Breuk.
        /// </summary>
        /// <remarks>Breuk wordt hierna niet vereenvoudigt.</remarks>
        /// <param name="breuk">Een <see cref="Breuk"/>.</param>
        public void Optellen(Breuk breuk)
        {
            this.Teller = this.teller * breuk.noemer + breuk.teller * this.noemer;
            this.Noemer = this.noemer * breuk.noemer;
        }

        /// <summary>
        /// Trekt de gegeven Breuk af van deze Breuk.
        /// </summary>
        /// <remarks>Breuk wordt hierna niet vereenvoudigt.</remarks>
        /// <param name="breuk">Een <see cref="Breuk"/>.</param>
        public void Aftrekken(Breuk breuk)
        {
            this.Teller = this.teller * breuk.noemer - breuk.teller * this.noemer;
            this.Noemer = this.noemer * breuk.noemer;
        }

        /// <summary>
        /// Vermenigvuldigt de Breuk met de gegeven Breuk.
        /// </summary>
        /// <remarks>Breuk wordt hierna niet vereenvoudigt.</remarks>
        /// <param name="breuk">Een <see cref="Breuk"/>.</param>
        public void Vermenigvuldig(Breuk breuk)
        {
            this.teller *= breuk.teller;
            this.noemer *= breuk.noemer;
        }

        /// <summary>
        /// Deelt de Breuk door de gegeven Breuk.
        /// </summary>
        /// <remarks>Breuk wordt hierna niet vereenvoudigt.</remarks>
        /// <param name="breuk">Een <see cref="Breuk"/>.</param>
        public void Deel(Breuk breuk)
        {
            this.teller *= breuk.noemer;
            this.noemer *= breuk.teller;
        }

        /// <summary>
        /// Vergroot de breuk met het meegegeven getal
        /// </summary>
        /// <param name="getal">het getal waarmee de breuk verhoogd word</param>
        public void VergrootBreuk(int getal)
        {
            this.teller *= getal;
            this.noemer *= getal;
        }

        /// <summary>
        /// Zorgt ervoor dat de noemers gelijk zijn.
        /// </summary>
        /// <param name="breuk">De Breuk waarmee er gelijk gezet moet worden.</param>
        public void zetBreukenGelijk(Breuk breuk)
        {
            if (this.Noemer != breuk.Noemer)
            {
                int tempnoem = this.noemer;
                this.VergrootBreuk(breuk.Noemer);
                breuk.VergrootBreuk(tempnoem);
            }
        }

        /// <summary>
        /// Geeft de Breuk terug als een double afgerond op Precisie aantal getallen.
        /// </summary>
        /// <returns>Een Double van de Breuk afgerond op Precisie aantal getallen.</returns>
        /// <param name="precisie">Een <see langword="integer"/> welke aangeeft hoeveel getallen er na de komma moeten verschijnen.</param>
        public double DecimaleWaarde(int precisie)
        {
            double d = (double)teller / (double)noemer;
            return Math.Round(d, precisie);
        }

        /// <summary>
        /// Geeft de Breuk terug als een double.
        /// </summary>
        /// <returns>Een double van de Breuk.</returns>
        public double DecimaleWaarde()
        {
            double d = (double)teller / (double)noemer;
            return d;
        }

        /// <summary>
        /// Vergelijkt deze breuk met een andere.
        /// </summary>
        /// <param name="breuk">Breuk waarmee er vergeleken moet worden</param>
        /// <returns><see langword="integer"/><![CDATA[
        /// > 0 als groter
        /// < 0 als kleiner
        /// 0 als gelijk]]>
        /// </returns>
        public int CompareTo(Breuk breuk)
        {
            return (int)(DecimaleWaarde() - breuk.DecimaleWaarde());
        }

        /// <summary>
        /// Geeft de Breuk weer als een String.
        /// </summary>
        /// <remarks>Als de <see cref="noemer"/> 1 is wordt deze niet weergegeven.</remarks>
        /// <returns>Een <see langword="string"/> welke de Breuk weergeeft.</returns>
        /// <example>Voorbeelden:
        /// 5/10
        /// 5/1 wordt 5
        /// </example>
        public override string ToString()
        {
            int t = teller;
            if (noemer < 0)
            {
                t *= -1;
            }
            return t + (noemer != 1 ? "/" + Math.Abs(noemer) : "");
        }

        /* Operators overloading */
        ///<summary>
        /// Operator overloading voor <![CDATA[<=]]>
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator <=(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) <= ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor <![CDATA[<]]>
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator <(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) < ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor >=
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator >=(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) >= ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor >
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator >(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) > ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor ==
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator ==(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) == ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor ==
        /// </summary>
        /// <returns>een Boolean.</returns>
        public static bool operator !=(Breuk b1, Breuk b2)
        {
            return (((double)b1.Teller / (double)b1.Noemer) != ((double)b2.teller / (double)b2.noemer));
        }

        ///<summary>
        /// Operator overloading voor +
        /// </summary>
        /// <returns>een Breuk.</returns>
        public static Breuk operator +(Breuk b1, Breuk b2)
        {
            Breuk returnBreuk = new Breuk(b1.Teller, b1.Noemer);
            returnBreuk.Optellen(b2);
            return returnBreuk;
        }

        ///<summary>
        /// Operator overloading voor -
        /// </summary>
        /// <returns>een Breuk.</returns>
        public static Breuk operator -(Breuk b1, Breuk b2)
        {
            Breuk returnBreuk = new Breuk(b1.Teller, b1.Noemer);
            returnBreuk.Aftrekken(b2);
            return returnBreuk;
        }

        ///<summary>
        /// Operator overloading voor *
        /// </summary>
        /// <returns>een Breuk.</returns>
        public static Breuk operator *(Breuk b1, Breuk b2)
        {
            Breuk returnBreuk = new Breuk(b1.Teller, b1.Noemer);
            returnBreuk.Vermenigvuldig(b2);
            return returnBreuk;
        }

        ///<summary>
        /// Operator overloading voor /
        /// </summary>
        /// <returns>een Breuk.</returns>
        public static Breuk operator /(Breuk b1, Breuk b2)
        {
            Breuk returnBreuk = new Breuk(b1.Teller, b1.Noemer);
            returnBreuk.Deel(b2);
            return returnBreuk;
        }

        /// <summary>
        /// overide equals function
        /// </summary>
        /// <returns>Een boolean</returns>
        public override bool Equals(object o)
        {
            return this == (Breuk)o;
        }

        /// <summary>
        /// overide hashcode function
        /// </summary>
        /// <returns>Een int met de hashcode</returns>
        public override int GetHashCode()
        {
            return ((double)Teller / (double)Noemer).GetHashCode();
        }
        /* Einde overrides */
    }
}
